package com.dxysun.spring.servlet.v3;

import com.dxysun.annotation.DXYAutowired;
import com.dxysun.annotation.DXYController;
import com.dxysun.annotation.DXYRequestMapping;
import com.dxysun.annotation.DXYRequestParam;
import com.dxysun.annotation.DXYService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.regex.Pattern;

public class DxyDispatchServlet extends HttpServlet
{
	private Properties properties = new Properties();

	private List<String> classNames = new ArrayList<>();

	private Map<String, Object> ioc = new HashMap<>();

	private List<Handler> handlerList = new ArrayList<>();

	@Override
	public void init(ServletConfig config) throws ServletException
	{
		super.init(config);
		// 1、加载配置
		doLoadConfig(config.getInitParameter("contextConfigLocation"));

		// 2、扫描配置下的类
		doScanner(properties.getProperty("scanPackage"));

		// 3、将扫描到类实例化，加到ioc容器里
		doInstance();

		// 4、依赖注入
		doAutowired();

		// 5、初始化handlerMapping
		doInitHandleMapping();

		System.out.println("DXY MVC Framework is init");
	}

	private void doInitHandleMapping()
	{
		for (Object object : ioc.values())
		{
			Class<?> clazz = object.getClass();
			if (clazz.isAnnotationPresent(DXYRequestMapping.class))
			{
				DXYRequestMapping dxyRequestMapping = clazz.getAnnotation(DXYRequestMapping.class);
				String baseUrl = dxyRequestMapping.value();
				Method[] methods = clazz.getDeclaredMethods();
				for (Method method : methods)
				{
					if (method.isAnnotationPresent(DXYRequestMapping.class))
					{
						DXYRequestMapping dxyMethodRequestMapping = method.getAnnotation(DXYRequestMapping.class);
						String url = baseUrl + "/" + dxyMethodRequestMapping.value();
						Pattern pattern = Pattern.compile(url.replaceAll("/+", "/"));
						handlerList.add(new Handler(
								ioc.get(toFistLowerCase(method.getDeclaringClass().getSimpleName())), method, pattern));
						System.out.println("Mapped " + url + "," + method);
					}
				}
			}
		}
	}

	private void doAutowired()
	{
		try
		{
			for (Object object : ioc.values())
			{
				if (object == null)
					continue;
				if (object.getClass().isAnnotationPresent(DXYController.class))
				{
					Field[] fields = object.getClass().getDeclaredFields();
					for (Field field : fields)
					{
						if (field.isAnnotationPresent(DXYAutowired.class))
						{
							DXYAutowired dxyAutowired = field.getAnnotation(DXYAutowired.class);
							String beanName = dxyAutowired.value();
							if ("".endsWith(beanName))
							{
								beanName = field.getType().getSimpleName();
							}
							Object instance = ioc.get(toFistLowerCase(beanName));
							if (instance == null)
							{
								continue;
							}
							field.setAccessible(true);
							field.set(object, instance);
						}
					}
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
			throw new RuntimeException("依赖注入异常");
		}
	}

	private void doInstance()
	{
		try
		{
			for (String className : classNames)
			{
				if (!className.contains("."))
				{
					continue;
				}
				Class<?> clazz = Class.forName(className);
				if (clazz.isAnnotationPresent(DXYController.class))
				{
					Object object = clazz.newInstance();
					String beanName = clazz.getSimpleName();
					ioc.put(toFistLowerCase(beanName), object);
				}
				else if (clazz.isAnnotationPresent(DXYService.class))
				{
					DXYService dxyService = clazz.getAnnotation(DXYService.class);
					String beanName = dxyService.value();
					if ("".endsWith(beanName))
					{
						beanName = clazz.getSimpleName();
					}
					Object instance = clazz.newInstance();
					ioc.put(toFistLowerCase(beanName), instance);
					for (Class<?> i : clazz.getInterfaces())
					{
						ioc.put(toFistLowerCase(i.getSimpleName()), instance);
					}
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
			throw new RuntimeException("实例化异常");
		}

	}

	private String toFistLowerCase(String beanName)
	{
		char[] chars = beanName.toCharArray();
		chars[0] += 32;
		return String.valueOf(chars);
	}

	private void doScanner(String scanPackage)
	{
		String path = scanPackage.replaceAll("\\.", "/");
		URL url = this.getClass().getClassLoader().getResource(path);
		if (url == null)
		{
			return;
		}
		File dirFile = new File(url.getPath());
		for (File file : Objects.requireNonNull(dirFile.listFiles()))
		{
			if (file.isDirectory())
			{
				doScanner(scanPackage + "." + file.getName());
			}
			if (file.getName().endsWith(".class"))
			{
				String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
				classNames.add(clazzName);
			}
		}
	}

	private void doLoadConfig(String contextLocation)
	{
		try (InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextLocation))
		{
			properties.load(in);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			throw new RuntimeException("获取配置异常");
		}
	}

	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		doPost(req, resp);
	}

	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		try
		{
			doDispatch(request, response);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			response.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
		}
	}

	public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception
	{
		String url = request.getRequestURI();
		String contextPath = request.getContextPath();
		url = url.replace(contextPath, "").replaceAll("/+", "/");
		Handler handler = getHandler(url);
		if (handler == null)
		{
			response.getWriter().write(url + " 404 Not Found!!");
			return;
		}
		Map<String, Integer> paramIndexMapping = handler.paramIndexMapping;
		Class<?>[] parameterTypes = handler.method.getParameterTypes();
		Object[] paramValues = new Object[parameterTypes.length];

		paramValues[paramIndexMapping.get(HttpServletRequest.class.getSimpleName())] = request;
		paramValues[paramIndexMapping.get(HttpServletResponse.class.getSimpleName())] = response;

		Map<String, String[]> paramsMap = request.getParameterMap();

		for (Map.Entry<String, String[]> param : paramsMap.entrySet())
		{
			// [1 , 2 , 3]
			String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","")
					.replaceAll("\\s","");

			//如果找到匹配的对象，则开始填充参数值
			if (!handler.paramIndexMapping.containsKey(param.getKey()))
			{
				continue;
			}
			int index = handler.paramIndexMapping.get(param.getKey());
			paramValues[index] = convert(parameterTypes[index], value);
		}

		handler.method.invoke(handler.controller, paramValues);
	}

	public Handler getHandler(String url)
	{
		for (Handler handler : handlerList)
		{
			if (handler.pattern.matcher(url).matches())
			{
				return handler;
			}
		}
		return null;
	}

	//url传过来的参数都是String类型的，HTTP是基于字符串协议
	//只需要把String转换为任意类型就好
	private Object convert(Class<?> type, String value)
	{
		if (Integer.class == type)
		{
			return Integer.valueOf(value);
		}
		//如果还有double或者其他类型，继续加if
		//这时候，我们应该想到策略模式了
		//在这里暂时不实现，希望小伙伴自己来实现
		return value;
	}

	private class Handler
	{
		Object controller;
		Method method;
		Pattern pattern; //${} url占位符解析
		Map<String, Integer> paramIndexMapping; //参数顺序

		public Handler(Object controller, Method method, Pattern pattern)
		{
			this.controller = controller;
			this.method = method;
			this.pattern = pattern;
			paramIndexMapping = new HashMap<>();
			initParamIndexMapping(method);

		}

		private void initParamIndexMapping(Method method)
		{
			Annotation[][] annotations = method.getParameterAnnotations();
			for (int i = 0; i < annotations.length; i++)
			{
				for (Annotation annotation : annotations[i])
				{
					if (annotation instanceof DXYRequestParam)
					{
						DXYRequestParam dxyRequestParam = (DXYRequestParam) annotation;
						String name = dxyRequestParam.value();
						if (!"".endsWith(name))
						{
							paramIndexMapping.put(name, i);
						}
					}
				}
			}

			Class<?>[] clazzs = method.getParameterTypes();
			for (int i = 0; i < clazzs.length; i++)
			{
				if (clazzs[i] == HttpServletRequest.class || clazzs[i] == HttpServletResponse.class)
				{
					paramIndexMapping.put(clazzs[i].getSimpleName(), i);
				}
			}
		}
	}

}
